// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework.httpfile;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;

import javax.servlet.http.HttpServletResponse;

import com.eva.epc.common.util.CommonUtils;

/**
 * 实现客户端的Http文件下载的实用类。
 * <p>
 * <b>补充：</b>客户端上传文件时，Android、Web其实是可以通过在上传URL中将参数带过来，但对于iOS
 * 端的 AFNetworking 3.0库来说这样做会直接导致客户端卡死（可能是AFN的bug，也可能是ios
 * 的bug）  ，所以目前RainbowChat服务端的文件上传处理类中，是从Multipart Content中读
 * 取参数（而不是url的参数），客户端也应该把参数放到Multipart Content中传过来，而不是url中。
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 */
public class FileDownload
{
	public static final String CONTENT_TYPE_DEFAULT = "application/octet-stream;charset=UTF-8";
	public static final String CONTENT_TYPE_JPEG = "image/jpeg";
	public static final String CONTENT_TYPE_MP4 = "video/mp4";
	public static final String CONTENT_TYPE_MP3 = "audio/mpeg";
	
	/**
	 * 以在服务端的文件名方式直接下载给客户端.
	 * @param res
	 * @param fileRealPath
	 * @throws Exception
	 */
	public static void download(HttpServletResponse res,String fileRealPath)
		throws Exception
	{
		download(res,fileRealPath,null);
	}
	/**
	 * 以在服务端的文件名方式直接下载给客户端.
	 * @param res
	 * @param file 要下载的文件对象
	 * @throws Exception
	 */
	public static void download(HttpServletResponse res,File file)throws Exception
	{
		download(res,file,null);
	}
	
	/**
	 * 从服务端下载文件(给HTTP客户端).
	 * <br>
	 * Content-Type is default:{@link #CONTENT_TYPE_DEFAULT}.
	 * 
	 * @param res 响应实例
	 * @param fileRealPath 服务端的文件绝对路径
	 * @param displayName 客户端收到文件接收请求时将看到的文件显示名
	 * @throws Exception
	 */
	public static void download(HttpServletResponse res,String fileRealPath,String displayName) throws Exception
	{
		download(res,fileRealPath,displayName,CONTENT_TYPE_DEFAULT, true);
	}
	/**
	 * 从服务端下载文件(给HTTP客户端).
	 * <br>
	 * Content-Type is default:{@link #CONTENT_TYPE_DEFAULT}.
	 * 
	 * @param res 响应实例
	 * @param file 要下载的文件对象
	 * @param displayName 客户端收到文件接收请求时将看到的文件显示名
	 * @throws Exception
	 */
	public static void download(HttpServletResponse res,File file,String displayName) throws Exception
	{
		download(res,file,displayName,CONTENT_TYPE_DEFAULT, true);
	}
	
	/**
	 * 从服务端下载文件(给HTTP客户端).
	 * @param res 响应实例
	 * @param fileRealPath 服务端的文件绝对路径
	 * @param displayName 客户端收到文件接收请求时将看到的文件显示名
	 * @param contentType 为null则表示返回结果的header中不加contet-type属性
	 * @param usedForAttachment 此参数只对浏览器端调用时起效：true表示在http头是标记作为附件，否则将直接在浏览器里被打开，
	 * 有关Content-Disposition 属性的说明详见RFC1806：http://www.rfc-editor.org/rfc/rfc1806.txt
	 * @throws Exception
	 */
	public static void download(HttpServletResponse res,String fileRealPath
			,String displayName, String contentType, boolean usedForAttachment)
		throws Exception
	{
		download(res, new File(fileRealPath), displayName, contentType, usedForAttachment);
	}
	/**
	 * 从服务端下载文件(给HTTP客户端).
	 * @param res 响应实例
	 * @param file 要下载的文件对象
	 * @param displayName 客户端收到文件接收请求时将看到的文件显示名
	 * @param contentType 为null则表示返回结果的header中不加contet-type属性
	 * @param usedForAttachment 此参数只对浏览器端调用时起效：true表示在http头是标记作为附件，否则将直接在浏览器里被打开，
	 * 有关Content-Disposition 属性的说明详见RFC1806：http://www.rfc-editor.org/rfc/rfc1806.txt
	 * @throws Exception
	 */
	public static void download(HttpServletResponse res,File file
			,String displayName, String contentType, boolean usedForAttachment)throws Exception
	{
		Exception ex=null;
//		File file = new File(fileRealPath);
		if(!file.exists())
		{
			new RuntimeException("对不起,没有找到你要的文件!");   
		}
		
		//返回给客户看到的文件名
		String dispName;
		if(CommonUtils.isStringEmpty(displayName))
			dispName=file.getName();
		else
			dispName=displayName;
		
		// http头中放入文件大小（content-length属性就是文件大小）
		res.setContentLength((int)file.length());
		// 20161211: js修改为只要没有contenttype参数就不在返回结果里加content-type，
		// 如果需要以图片的方式下载，则请手动传参CONTENT_TYPE_DEFAULT
		if(!CommonUtils.isStringEmpty(contentType))
			res.setContentType(contentType);  
		
		// http头中放入文件名（支持中文文件名）
		// 【说明】：Content-Disposition 属性是作为对下载文件的一个标识字段，在rfc2616 章节19.5 Additional Features中
		//          有介绍，具体介绍请看 http://www.rfc-editor.org/rfc/rfc1806.txt，Content-Disposition属性有两种类
		//          型：inline 和 attachment inline ：将文件内容直接显示在页面 attachment：弹出对话框让用户下载
		res.setHeader("Content-disposition",(usedForAttachment?"attachment":"inline") + ";filename=\""
				//发给客户的数据先以GBK字符集转换成二进制再以ISO-8859-1的编码形式传回客户端
				//客户端的传过来的数据同(因而服务端要进行逆过程--先获ISO-8859-1的二进制再编码成GBK)
				+(isDoubleByteLang(dispName)?getGBK2ISO_8859_1Str(dispName):dispName)+"\"");   

		int length=(new Long(file.length())).intValue();   
		int bufferSize=1024;   
		BufferedOutputStream output= null;   
		BufferedInputStream input = null;   
		output = new BufferedOutputStream(res.getOutputStream());   
		input = new BufferedInputStream(new FileInputStream(file));   
		try  
		{
			int once = 0;   
			int total = 0;   
			byte[] buffer = new byte[bufferSize];   
			do   
			{
				once = input.read(buffer);   
				total += once;   
				if(once >= 0)   
					output.write(buffer,0,bufferSize);   
			}   
			while ((total<length)&&(once >= 0));   
			res.flushBuffer();   
		}
		catch(Exception e)//maybe user cancelled download   
		{
			ex = new Exception("下载出错了,"+e.getMessage());  
//			e.printStackTrace();
		}  
		finally   
		{   
			if(input!= null)   
				input.close();   
			if(output!=null)   
				output.close();   

			if(ex!=null)   
				throw ex;   
		}  
	}
	
	/**
	 * 是否双字节语言(字符集),本系统中全当中文(繁或简体)处理.
	 * @param s
	 * @return
	 */
	public static boolean isDoubleByteLang(String s)
	{
		if(s==null)
			s="";
		return s.length()<s.getBytes().length;
	}
	
	/**
	 * 将GBK字符以ISO-8859-1的编码方式编码成字符串
	 * <pre>如服务端通过HTTP协议传到客户端的字符数据时需此转换</pre>
	 * @param s
	 * @return
	 */
	public static String getGBK2ISO_8859_1Str(String s)
	{
		return convertEncoding(s,"GBK","ISO-8859-1");
	}
	
	/**
	 * 将字符串s以originalEncoding的编码方式取出并转换成编码destEncloding.
	 * @param s 原字符串
	 * @param originalEncoding 原编码
	 * @param destEncloding 目的编码
	 * @return 目的编码后的字符串
	 */
	public static String convertEncoding(String s,String originalEncoding,String destEncloding)
	{
		try
		{
			// 三者都不为空时才进行编码转换
			if(s!=null&&originalEncoding!=null&&destEncloding!=null)
				s=new String(s.getBytes(originalEncoding),destEncloding);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		return s;
	}
}